// Copyright 2007 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lichen.services.post.impl;

import java.util.Date;

import lichen.entities.forum.Forum;
import lichen.entities.forum.ForumImpl;
import lichen.entities.post.AbstractPost;
import lichen.entities.post.Comment;
import lichen.entities.post.Post;
import lichen.entities.post.Topic;
import lichen.services.post.PostService;

import org.hibernate.Query;
import org.hibernate.Session;

/**
 * 对贴子进行处理的服务类
 * @author <a href="mailto:jun.tsai@gmail.com">Jun Tsai</a>
 * @version $Revision: 7 $
 * @since 0.0.1
 */
public class PostServiceImpl implements PostService  {
	
//	update forum topic number
	private static final String UPDATE_FORUM_TOPIC_NUM_HQL="update "+ForumImpl.class.getName()+" f set f."+ForumImpl.TOPIC_NUM_PRO_NAME+"=f."+ForumImpl.TOPIC_NUM_PRO_NAME+"+1 where f."+ForumImpl.LEFT_PRO_NAME+"<=? and f."+ForumImpl.RIGHT_PRO_NAME+">=?";
//	update forum comment number
	private static final String UPDATE_FORUM_COMMENT_NUM_HQL="update "+ForumImpl.class.getName()+" f set f."+ForumImpl.COMMENT_NUM_PRO_NAME+"=f."+ForumImpl.COMMENT_NUM_PRO_NAME+"+1 where f."+ForumImpl.LEFT_PRO_NAME+"<=? and f."+ForumImpl.RIGHT_PRO_NAME+">=?";
	// update topic comment number
	private static final String UPDATE_TOPIC_COMMENT_NUMN_HQL="update "+Topic.class.getName()+" t set t."+Topic.COMMENT_NUM_PRO_NAME+"=t."+Topic.COMMENT_NUM_PRO_NAME+"+1 where t.id=?";
	private static final String QUERY_PARENT_COUNT_HQL = "select count(*) from "+AbstractPost.class.getName()+" p where p."+AbstractPost.PARENT_NODE_PRO_NAME+"=? "; 
			
	
	/**
	 * Hibernate Session Object
	 */
	private Session session;
	/**
	 * 通过给定的HibernateSession来构造Service.
	 * @param session
	 */
	public PostServiceImpl(Session session){
		this.session=session;
	}
	/**
	 * @see lichen.services.post.PostService#saveTopic(lichen.entities.post.Topic)
	 */
	public void saveTopic(Topic topic){
		topic.setThread(int2vancode(1)+"/");
		//创建时间
		topic.setCreateDate(new Date());
		session.saveOrUpdate(topic);
		
		//加入和自身的关联.
		topic.setParentTopic(topic); 
		session.saveOrUpdate(topic);
		
		//更新归属论坛的主题帖子数目.
		Forum forum=topic.getForum();
		
		this.updateForumPostNumByHQL(UPDATE_FORUM_TOPIC_NUM_HQL, forum);
	}
	/**
	 * @see lichen.services.post.PostService#saveComment(lichen.entities.post.Topic, lichen.entities.post.Post, lichen.entities.post.Comment)
	 */
	public void saveComment(Topic topic,Post parentPost, Comment comment) {
		//找到父节点
		Forum forum=parentPost.getForum();
		
		//找到父节点的字节点的个数
		Query query=session.createQuery(QUERY_PARENT_COUNT_HQL);
		query.setParameter(0, parentPost);
		//算法见:
		// http://cvs.drupal.org/viewcvs/drupal/drupal/modules/comment/comment.module?rev=1.561&view=markup
		// 的 comment_render 方法
		// Thanks Drupal CMS System
		
		/** To display threaded comments in the correct order we keep a 'thread' field
		 * and order by that value. This field keeps this data in
		 * a way which is easy to update and convenient to use.
		 *
		 * A "thread" value starts at "1". If we add a child (A) to this comment,
		 * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
		 * brother of (A) will get "1.2". Next brother of the parent of (A) will get
		 * "2" and so on.
		 *
		 * First of all note that the thread field stores the depth of the comment:
		 * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
		 *
		 * Now to get the ordering right, consider this example:
		 *
		 * 1
		 * 1.1
		 * 1.1.1
		 * 1.2
		 * 2
		 *
		 * If we "ORDER BY thread ASC" we get the above result, and this is the
		 * natural order sorted by time. However, if we "ORDER BY thread DESC"
		 * we get:
		 *
		 * 2
		 * 1.2
		 * 1.1.1
		 * 1.1
		 * 1
		 *
		 * Clearly, this is not a natural way to see a thread, and users will get
		 * confused. The natural order to show a thread by time desc would be:
		 *
		 * 2
		 * 1
		 * 1.2
		 * 1.1
		 * 1.1.1
		 *
		 * which is what we already did before the standard pager patch. To achieve
		 * this we simply add a "/" at the end of each "thread" value. This way out
		 * thread fields will look like depicted below:
		 *
		 * 1/
		 * 1.1/
		 * 1.1.1/
		 * 1.2/
		 * 2/
		 *
		 * we add "/" since this char is, in ASCII, higher than every number, so if
		 * now we "ORDER BY thread DESC" we get the correct order. However this would
		 * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
		 * to consider the trailing "/" so we use a substring only.
		 */
		
		long num=(Long)query.list().iterator().next();
		StringBuffer sb=new StringBuffer();
		sb.append(parentPost.getThread());
		sb.setLength(sb.length()-1);
		sb.append(".");
		sb.append(int2vancode(num+1));
		sb.append("/");
		
		comment.setThread(sb.toString());
		
		
		comment.setParentTopic(topic);
		comment.setParentNode(parentPost);
		comment.setForum(forum);
		//创建时间
		comment.setCreateDate(new Date());
		
		session.saveOrUpdate(comment);
		
		Query updateCommentNumQuery=session.createQuery(UPDATE_TOPIC_COMMENT_NUMN_HQL);
		updateCommentNumQuery.setParameter(0,topic.getId());
		updateCommentNumQuery.executeUpdate();
		
		this.updateForumPostNumByHQL(UPDATE_FORUM_COMMENT_NUM_HQL, forum);
	}
	private void updateForumPostNumByHQL(String hql,Forum forum){
		Query query=session.createQuery(hql);
		query.setParameter(0, forum.getLeft());
		query.setParameter(1, forum.getRight());
		query.executeUpdate();
		
	}
	/**
	 * @see lichen.services.post.PostService#increamViewNum(lichen.entities.post.Topic)
	 */
	public void increamViewNum(Topic topic) {
		topic.setViewNum(topic.getViewNum()+1);
		session.saveOrUpdate(topic);
		
	}
	/**
	 * Generate vancode.
	 *
	 * Consists of a leading character indicating length, followed by N digits
	 * with a numerical value in base 36. Vancodes can be sorted as strings
	 * without messing up numerical order.
	 *
	 * It goes:
	 * 10, 11, 12, ..., 1y, 1z,
	 * 210, 211, ... , 2zy, 2zz,
	 * 3100, 3101, ..., 3zzy, 3zzz,
	 * 41000, 41001, ...
	 */
	static String int2vancode(long i) {
		String num =Long.toString(i,36);
		int length = num.length();
		return Integer.toString(length,36)+num;
	}
	

	/**
	 * Decode vancode back to an integer.
	 */
	static long  vancode2int(String thread) {
	  return Long.parseLong(thread.substring(1), 36);
	}

}
